
/*
 * Copyright (C) 2010 Bitglue. All rights reserved.
 *
 * $Id: OsgiContainer.java bself $
 */

package com.bitglue.osgi;

import com.bitglue.osgi.model.Deployment;

import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.felix.framework.Felix;
import org.apache.felix.framework.Logger;
import org.apache.felix.framework.util.FelixConstants;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;

import org.slf4j.LoggerFactory;

import static org.osgi.framework.Constants.*;

/**
 * OsgiContainer wraps the OSGI container configuration
 * with java bean style methods which is especially
 * suitable for spring frameworks to embed an OSGI container.
 * This class instance also supports manual installation
 * and uninstallation of bundles.
 * @author Bryan Patrick Self
 */
public class OsgiContainer implements Plugable {

    protected Map<String, Object> config;
    protected Felix felix;

    private final org.slf4j.Logger log = LoggerFactory.getLogger(getClass());
    private volatile boolean ready;
    public OsgiContainer() {
        config = new HashMap<String, Object>();
    }

    public void startContainer() throws Exception {
        BundleActivator activator = new BundleActivator() {
            private BundleContext context;
            public void start(BundleContext bc) {
                context = bc;
            }
            public void stop(BundleContext bc) {
                context = null;
            }
            public BundleContext getContext() {
                return context;
            }
        };
        List<Object> list = new LinkedList<Object>();
        list.add(activator);
        config.put(FelixConstants.SYSTEMBUNDLE_ACTIVATORS_PROP, list);

        felix = new Felix(config);
        felix.start();
        log.info("embedded osgi container successfully started");
        ready = true;
        felix.waitForStop(0);
    }

    public void setBundleCacheDir(String dir) {
        config.put(FRAMEWORK_STORAGE, dir);
    }

    public String getBundleCacheDir() {
        return (String)config.get(FRAMEWORK_STORAGE);
    }

    public void setClearCacheDirOnStart(boolean cos) {
        config.put(FRAMEWORK_STORAGE_CLEAN, cos ?
          FRAMEWORK_STORAGE_CLEAN_ONFIRSTINIT : "none");
    }

    public boolean getClearCacheDirOnStart() {
        String res = (String)config.get(FRAMEWORK_STORAGE_CLEAN);
        return res != null && res.equals(
          FRAMEWORK_STORAGE_CLEAN_ONFIRSTINIT) ? true : false;
    }

    public void setSystemPackages(List<String> pkgList) {
        StringBuilder sb = new StringBuilder();
        for (String s : pkgList)
            sb.append(s + ",");
        config.put(FRAMEWORK_SYSTEMPACKAGES,
          sb.toString().replaceAll(",$", ""));
    }

    public List<String> getSystemPackages() {
        String s = (String)config.get(FRAMEWORK_SYSTEMPACKAGES);
        return Arrays.asList(s.split(","));
    }

    public void setSystemPackagesExtra(List<String> pkgList) {
        StringBuilder sb = new StringBuilder();
        for (String s : pkgList)
            sb.append(s + ",");
        config.put(FRAMEWORK_SYSTEMPACKAGES_EXTRA,
          sb.toString().replaceAll(",$", ""));
    }

    public List<String> getSystemPackagesExtra() {
        String s = (String)config.get(FRAMEWORK_SYSTEMPACKAGES_EXTRA);
        return Arrays.asList(s.split(","));
    }

    public void setLogger(Logger logger) {
        config.put(FelixConstants.LOG_LOGGER_PROP, logger);
    }

    public Logger getLogger() {
        return (Logger)config.get(FelixConstants.LOG_LOGGER_PROP);
    }

    public void setLogLevel(int level) {
        config.put(FelixConstants.LOG_LEVEL_PROP, level);
    }

    public int getLogLevel() {
        Integer res = (Integer)config.get(FelixConstants.LOG_LEVEL_PROP);
        return res.intValue();
    }

    public void setStartLevel(int level) {
        config.put(FRAMEWORK_BEGINNING_STARTLEVEL, level);
    }

    public int getStartLevel() {
        Integer res = (Integer)config.get(FRAMEWORK_BEGINNING_STARTLEVEL);
        return res.intValue();
    }

    public boolean installModule(Deployment d) {
        String mod = d.getModule() + " (" + d.getVersion() + ")";
        String pre = "trying to install " + mod + " ";
        log.debug("install of " + mod);
        BundleContext context = felix.getBundleContext();
        File file = new File(d.getRepoEntry());
        try {
            String uri = file.toURI().toString();
            Bundle bundle = getExistingBundle(context, file);
            if (bundle != null) {
                log.warn(pre + "but it is already installed");
                return false;
            }
            context.installBundle(uri);
            return true;
        } catch (BundleException be) {
            log.error(pre + "but " + be
              + " was thrown->" + be.getCause());
            return false;
        }
    }

    public boolean isReady() {
        return ready;
    }

    /**
     * OsgiAction represents an osgi container operation.
     */
    private interface OsgiAction {
        void execute(Bundle bundle, String op) throws BundleException;
    }

    public boolean containerOp(String op, Deployment d, OsgiAction act) {
        String mod = d.getModule() + " (" + d.getVersion() + ")";
        String pre = "trying to " + op + " " + mod + " ";
        log.debug(op + " of " + mod);
        BundleContext context = felix.getBundleContext();
        File file = new File(d.getRepoEntry());
        Bundle bundle = getExistingBundle(context, file);
        if (bundle == null) {
            log.warn(pre + "but no matching instance is found");
            return false;
        }
        if (bundle.getBundleId() == 0) {
            log.warn(pre + "but bundle id was zero");
            return false;
        }
        try {
            act.execute(bundle, op);
            return true;
        } catch (BundleException be) {
            log.error(pre + "but " + be
              + " was thrown->" + be.getCause());
            return false;
        }
    }

    public boolean reInstallModule(Deployment oldDeploy,
      Deployment newDeploy) {
        boolean ret = containerOp("uninstall", oldDeploy,
          new OsgiAction() {
            public void execute(Bundle bundle, String op)
              throws BundleException {
                if (!isFragment(bundle))
                    bundle.stop();
                bundle.uninstall();
            }
        });
        if (ret)
          ret = installModule(newDeploy);
        return ret;
    }

    public boolean uninstallModule(Deployment d) {
        return containerOp("uninstall", d, new OsgiAction() {
            public void execute(Bundle bundle, String op)
              throws BundleException {
                bundle.uninstall();
            }
        });
    }

    public boolean startModule(Deployment d) {
        return containerOp("start", d, new OsgiAction() {
            public void execute(Bundle bundle, String op)
              throws BundleException {
                if (!isFragment(bundle))
                    bundle.start();
            }
        });
    }

    public boolean stopModule(Deployment d) {
        return containerOp("stop", d, new OsgiAction() {
            public void execute(Bundle bundle, String op)
              throws BundleException {
                if (!isFragment(bundle))
                    bundle.stop();
            }
        });
    }

    public Bundle getExistingBundle(BundleContext context, File file) {
        String uri = file.toURI().toString();
        for (Bundle b : context.getBundles()) {
            if (b.getLocation().equals(uri))
                return b;
        }
        return null;
    }

    protected boolean isFragment(Bundle bundle) {
        return bundle.getHeaders().get(FRAGMENT_HOST) != null;
    }

}
